package br.com.ffm.mtfw.classes;

import br.com.ffm.mtfw.exceptions.InvalidSelectOperationException;
import br.com.ffm.mtfw.exceptions.InvalidSelectTenantParametersException;
import br.com.ffm.mtfw.exceptions.CreateTenantTableException;
import br.com.ffm.mtfw.exceptions.AlterTenantUserTableException;
import br.com.ffm.mtfw.exceptions.InsertTenantTableException;
import br.com.ffm.mtfw.exceptions.InvalidOperationException;
import br.com.ffm.mtfw.exceptions.MissingTenantUserAnnotationException;
import br.com.ffm.mtfw.exceptions.NullTenantException;
import br.com.ffm.mtfw.interfaces.TenantUser;
import br.com.ffm.mtfw.interfaces.Tenantable;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Class that executes the multi-tenant's insert, update, delete and select
 * querys
 *
 * @author matheus
 */
public class TenantPreparedStatement {

    private PreparedStatement ps;
    private final String TENANT_TABLE_NAME = "TENANT";
    private final String TENANT_COLUMN_NAME = "TENANT_NAME";
    private final String TENANT_COLUMN_NAME_TYPE = "VARCHAR";
    private final int TENANT_COLUMN_NAME_SIZE = 200;

    /**
     * Executes a select query and returns the ResultSet of the query.
     * <br/><br/> <b>Note:</b> To execute this method correctly it's necessary
     * to call the executeInsertQueryPreparedStatement method to each Tenantable
     * class.
     *
     * @param con A JDBC connection (session) with a specific database
     * @param args A map that uses a tenantable class as a key to the column
     * values. If there's more than one key (tenantable class) the select will
     * make a join between the tables.
     * @return A ResultSet of the prepared statement executeQuery method.
     * @throws InvalidOperationException If some attributeName of the
     * ColumnValue doesn't exist or if the types of the attributeName and
     * attributeValue of the class ColumnValue doesn't match.
     * @throws InvalidSelectTenantParametersException If there's more than one
     * tenant in the objects in the parameters
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    public ResultSet executeSelectQueryPreparedStatement(Connection con,
            LinkedHashMap<Tenantable, List<ColumnValue>> args) throws InvalidSelectTenantParametersException, InvalidOperationException, MissingTenantUserAnnotationException, NullTenantException {
        List<Object> parameters = new ArrayList<Object>();
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT * FROM ");
        this.putParametersQuery(sql, args, parameters);

        try {
            ps = con.prepareStatement(sql.toString());
            int count = 1;
            for (Object parameter : parameters) {
                ps.setObject(count, parameter);
                count++;
            }
            return ps.executeQuery();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(sql.toString());
        }
    }

    /**
     * Executes a select query and returns the ResultSet of the query ordered by
     * a column. <br/><br/> <b>Note:</b> To execute this method correctly it's
     * necessary to call the executeInsertQueryPreparedStatement method to each
     * Tenantable class.
     *
     * @param con A JDBC connection (session) with a specific database
     * @param args A map that uses a tenantable class as a key to the column
     * values. If there's more than one key (tenantable class) the select will
     * make a join between the tables.
     * @param orderBy A TenantColumn object that contains the column that will
     * order the query and the Tenantable class that contais the table name
     * where the column is.
     * @return A ResultSet of the prepared statement executeQuery method.
     * @throws InvalidOperationException If some attributeName of the
     * ColumnValue doesn't exist, if the types of the attributeName and
     * attributeValue of the class ColumnValue doesn't match or if the tenant in
     * the TenantColumn is different from the others tenants in the objects
     * @throws InvalidSelectTenantParametersException If there's more than one
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared tenant in the objects in the parameters
     * @throws NullTenantException If the getTenant() method returns null
     */
    public ResultSet executeSelectQueryPreparedStatement(Connection con, LinkedHashMap<Tenantable, List<ColumnValue>> args, TenantColumn orderBy) throws InvalidSelectTenantParametersException, InvalidOperationException, MissingTenantUserAnnotationException, NullTenantException {
        List<Object> parameters = new ArrayList<Object>();
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT * FROM ");
        this.putParametersQuery(sql, args, parameters);
        if (orderBy != null) {
            sql.append(" ORDER BY ");
            String tableName = null;
            try {
                tableName = orderBy.getTenant().getClass().getAnnotation(TenantUser.class).tableName();
            } catch (NullPointerException ex) {
                throw new MissingTenantUserAnnotationException();
            }
            sql.append(tableName);
            sql.append(".");
            sql.append(orderBy.getColumn());
        }

        try {
            ps = con.prepareStatement(sql.toString());
            int count = 1;
            for (Object parameter : parameters) {
                ps.setObject(count, parameter);
                count++;
            }
            return ps.executeQuery();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(sql.toString());
        }
    }

    /**
     * Executes a select query and returns the ResultSet of the query.
     * <br/><br/> <b>Note:</b> To execute this method correctly it's necessary
     * to call the executeInsertQueryPreparedStatement method to each Tenantable
     * class.
     *
     * @param selectOperation - A SelectOperation that constains the
     * OperationType (ex: count) and the column
     * @param con - A JDBC connection (session) with a specific database
     * @param args - A map that uses a tenantable class as a key to the column
     * values. If there's more than one key (tenantable class) the select will
     * make a join between the tables.
     * @return A ResultSet of the prepared statement executeQuery method.
     * @throws InvalidOperationException If some attributeName of the
     * ColumnValue doesn't exist or if the types of the attributeName and
     * attributeValue of the class ColumnValue doesn't match
     * @throws InvalidSelectTenantParametersException If there's more than one
     * tenant in the objects in the parameters
     * @throws InvalidSelectOperationException If there's not at least one
     * TenantColumn in the SelectOperation and the operation is not the SQL
     * count
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    public ResultSet executeSelectQueryPreparedStatement(SelectOperation selectOperation, Connection con, LinkedHashMap<Tenantable, List<ColumnValue>> args) throws InvalidSelectOperationException, InvalidSelectTenantParametersException, InvalidOperationException, MissingTenantUserAnnotationException, NullTenantException {
        List<Object> parameters = new ArrayList<Object>();
        StringBuilder sql = new StringBuilder();
        sql.append("SELECT ");
        this.putSelectOperations(selectOperation, sql);
        sql.append(" FROM ");
        this.putParametersQuery(sql, args, parameters);
        try {
            ps = con.prepareStatement(sql.toString());
            int count = 1;
            for (Object parameter : parameters) {
                ps.setObject(count, parameter);
                count++;
            }
            return ps.executeQuery();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(sql.toString());
        }
    }

    /**
     * Method that get a SQL query and put a select operation on it
     *
     * @param selectOperation Object Select Operation that contains the
     * operations count, avg, max, min and sum
     * @param sql Sql to be modified
     * @throws InvalidSelectOperationException If there's not at least one
     * TenantColumn in the SelectOperation and the operation is not the SQL
     * count
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    private void putSelectOperations(SelectOperation selectOperation, StringBuilder sql) throws InvalidSelectOperationException, MissingTenantUserAnnotationException {
        if (selectOperation != null) {
            if (selectOperation.isTenantColumnNull()) {
                if (selectOperation.isCountOperation()) {
                    sql.append("COUNT(*)");
                } else {
                    throw new InvalidSelectOperationException();
                }
            } else {
                if (!selectOperation.isNullOperationType()) {
                    sql = this.identifyOperationType(selectOperation, sql);
                    sql.append("(");
                    String tableName = null;
                    try {
                        tableName = selectOperation.getTenantColumn().getTenant().getClass().getAnnotation(TenantUser.class).tableName();
                    } catch (NullPointerException ex) {
                        throw new MissingTenantUserAnnotationException();
                    }
                    sql.append(tableName);
                    sql.append(".");
                    sql.append(selectOperation.getTenantColumn().getColumn());
                    sql.append(")");
                }
            }
        } else {
            sql.append("*");
        }
    }

    /**
     * Add to the StringBuilder a operation defined in the object
     * SelectOperation
     *
     * @param selectOperation A SelectOperation with a OperationType
     * @param sql StringBuilder with the query to be added a SQL operation
     * @return The query with the operation added
     */
    private StringBuilder identifyOperationType(SelectOperation selectOperation, StringBuilder sql) {
        if (selectOperation.isCountOperation()) {
            sql.append("COUNT");
        } else if (selectOperation.isAvgOperation()) {
            sql.append("AVG");
        } else if (selectOperation.isMaxOperation()) {
            sql.append("MAX");
        } else if (selectOperation.isMinOperation()) {
            sql.append("MIN");
        } else if (selectOperation.isSumOperation()) {
            sql.append("SUM");
        }
        return sql;
    }

    /**
     * Method that makes a loop for the ColumnValues and the parameters to build
     * the query and put in the StringBuilder
     *
     * @param sql StringBuilder with the query to be added the columns and
     * values
     * @param args A map that uses a tenantable class as a key to the column
     * values. If there's more than one key (tenantable class) the select will
     * make a join between the tables.
     * @param parameters A list with the values that will be added the
     * attributeValues and tenant ids
     * @throws InvalidSelectTenantParametersException If there's more than one
     * tenant in the objects in the parameters
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    private void putParametersQuery(StringBuilder sql, Map<Tenantable, List<ColumnValue>> args, List<Object> parameters) throws InvalidSelectTenantParametersException, MissingTenantUserAnnotationException, NullTenantException {
        String tenantName = null;
        Set<Tenantable> tenants = args.keySet();
        for (Tenantable tenantUser : tenants) {
            String name = null;
            try {
                name = tenantUser.getTenant().getId();
            } catch (NullPointerException ex) {
                throw new NullTenantException();
            }
            if (tenantName != null && !tenantName.equals(name)) {
                throw new InvalidSelectTenantParametersException();
            }
            String tableName = null;
            try {
                tableName = tenantUser.getClass().getAnnotation(TenantUser.class).tableName();
            } catch (NullPointerException ex) {
                throw new MissingTenantUserAnnotationException();
            }
            sql.append(tableName);
            sql.append(",");
            sql.append(name.substring(1, 1));
            sql.append(" ");
            tenantName = name;
        }
        sql.append(TENANT_TABLE_NAME);
        sql.append(" WHERE ");
        int countTenants = 1;
        int tenantsSize = tenants.size();
        for (Tenantable tenantUser : tenants) {
            List<ColumnValue> values = args.get(tenantUser);
            if (values != null) {
                for (ColumnValue column : values) {
                    String tableName = null;
                    try {
                        tableName = tenantUser.getClass().getAnnotation(TenantUser.class).tableName();
                    } catch (NullPointerException ex) {
                        throw new MissingTenantUserAnnotationException();
                    }
                    sql.append(tableName);
                    sql.append(".");
                    sql.append(column.getAttributeName());
                    sql.append(" = ");
                    if (column.getTenantable() != null) {
                        try {
                            tableName = column.getTenantable().getClass().getAnnotation(TenantUser.class).tableName();
                        } catch (NullPointerException ex) {
                            throw new MissingTenantUserAnnotationException();
                        }
                        sql.append(tableName);
                        sql.append(".");
                        sql.append(column.getAttributeValue());
                        sql.append(" AND ");
                    } else {
                        parameters.add(column.getAttributeValue());
                        sql.append("?");
                        sql.append(" AND ");
                    }
                }
            }
            sql.append(TENANT_TABLE_NAME);
            sql.append(".");
            sql.append(TENANT_COLUMN_NAME);
            sql.append(" = ");
            String tableName = null;
            try {
                tableName = tenantUser.getClass().getAnnotation(TenantUser.class).tableName();
            } catch (NullPointerException ex) {
                throw new MissingTenantUserAnnotationException();
            }
            sql.append(tableName);
            sql.append(".");
            sql.append(TENANT_COLUMN_NAME);
            if (countTenants < tenantsSize) {
                sql.append(" AND ");
            }
            countTenants++;
        }
        sql.append(" AND ");
        sql.append(TENANT_TABLE_NAME);
        sql.append(".");
        sql.append(TENANT_COLUMN_NAME);
        sql.append(" = ");
        parameters.add(tenantName);
        sql.append("?");
    }

    /**
     * Executes a insert query to the table declared in the Tenantable class.
     * <b>NOTE: </b> the sql insert must be a normal sql, without tenant
     * references.
     *
     * @param sqlInsert SQL insert query. Ex: select * from users where id = ?
     * @param params The values of the '?' params <b>in order</b>.
     * @param classToInsert The class that represents the table in the sql
     * insert query.
     * @param conn A JDBC Connection
     * @throws CreateTenantTableException If there's something wrong at the
     * connection or the permissions of the database user
     * @throws InsertTenantTableException If there's something wrong at the
     * connection or the permissions of the database user
     * @throws AlterTenantUserTableException If there's something wrong at the
     * connection or the permissions of the database user
     * @throws InvalidOperationException If the parameters are not set correctly
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    public void executeInsertQueryPreparedStatement(String sqlInsert, List<Object> params, Tenantable classToInsert, Connection conn) throws CreateTenantTableException, InsertTenantTableException, AlterTenantUserTableException, InvalidOperationException, MissingTenantUserAnnotationException, NullTenantException {
        StringBuilder newSqlInsert = new StringBuilder();
        try {
            String tenantName = null;
            try {
                tenantName = classToInsert.getTenant().getId();
            } catch (NullPointerException ex) {
                throw new NullTenantException();
            }
            String tableName = null;
            try {
                tableName = classToInsert.getClass().getAnnotation(TenantUser.class).tableName();
            } catch (NullPointerException ex) {
                throw new MissingTenantUserAnnotationException();
            }
            this.createTenantTable(conn);
            if (this.verifyExistenceTenant(tenantName, conn) == false) {
                this.insertIntoTenantTable(tenantName, conn);
            }
            if (this.verifyExistenceColumnTenant(tableName, conn) == false) {
                this.insertIntoTenantUserTable(tableName, conn);
            }
            newSqlInsert.append(sqlInsert.replace(")", ""));
            newSqlInsert.append(",");
            newSqlInsert.append("?");
            newSqlInsert.append(")");
            ps = conn.prepareStatement(newSqlInsert.toString());

            int positionTenantColumn = this.getPositionTenantColumn(tableName, conn);
            int count = 0;
            while (count < params.size()) {
                if (count != positionTenantColumn) {
                    Object param = params.get(count);
                    count++;
                    ps.setObject(count, param);
                } else {
                    count++;
                }
            }
            ps.setString(positionTenantColumn, tenantName);
            ps.execute();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(newSqlInsert.toString());
        }
    }

    /**
     * Executes a update query to the table declared in the Tenantable class.
     * <b>NOTE: </b> the sql update must be a normal sql, without tenant
     * references.
     *
     * @param sqlUpdate SQL update query. Ex: update from users set id = ? where
     * id = ?
     * @param params The values of the '?' params <b>in order</b>.
     * @param classToUpdate The class that represents the table in the sql
     * update query.
     * @param conn A JDBC Connection
     * @throws InvalidOperationException If the parameters are not set correctly
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    public void executeUpdateQueryPreparedStatement(String sqlUpdate, List<Object> params, Tenantable classToUpdate, Connection conn) throws InvalidOperationException, MissingTenantUserAnnotationException, NullTenantException {
        StringBuilder newSqlUpdate = new StringBuilder();
        try {
            String tenantName = null;
            try {
                tenantName = classToUpdate.getTenant().getId();
            } catch (NullPointerException ex) {
                throw new NullTenantException();
            }
            String tableName = null;
            try {
                tableName = classToUpdate.getClass().getAnnotation(TenantUser.class).tableName();
            } catch (NullPointerException ex) {
                throw new MissingTenantUserAnnotationException();
            }
            newSqlUpdate.append(sqlUpdate);
            if (sqlUpdate.contains("WHERE")) {
                newSqlUpdate.append(" AND ");
            } else {
                newSqlUpdate.append(" WHERE ");
            }
            newSqlUpdate.append(tableName);
            newSqlUpdate.append(".");
            newSqlUpdate.append(TENANT_COLUMN_NAME);
            newSqlUpdate.append(" = ");
            newSqlUpdate.append("?");

            ps = conn.prepareStatement(newSqlUpdate.toString());
            int count = 1;
            for (Object param : params) {
                ps.setObject(count, param);
                count++;
            }
            ps.setString(count, tenantName);
            ps.execute();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(newSqlUpdate.toString());
        }
    }

    /**
     * Executes a update query to the table declared in the Tenantable class.
     * <b>NOTE: </b> the sql delete must be a normal sql, without tenant
     * references.
     *
     * @param sqlDelete SQL delete query. Ex: delete from users where id = ?
     * @param params The values of the '?' params <b>in order</b>.
     * @param classToDelete The class that represents the table in the sql
     * delete query.
     * @param conn A JDBC Connection
     * @throws InvalidOperationException If the parameters are not set correctly
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared
     * @throws NullTenantException If the getTenant() method returns null
     */
    public void executeDeleteQueryPreparedStatement(String sqlDelete, List<Object> params, Tenantable classToDelete, Connection conn) throws InvalidOperationException, MissingTenantUserAnnotationException, NullTenantException {
        StringBuilder newSqlDelete = new StringBuilder();
        try {
            String tenantName = null;
            try {
                tenantName = classToDelete.getTenant().getId();
            } catch (NullPointerException ex) {
                throw new NullTenantException();
            }
            String tableName = null;
            try {
                tableName = classToDelete.getClass().getAnnotation(TenantUser.class).tableName();
            } catch (NullPointerException ex) {
                throw new MissingTenantUserAnnotationException();
            }
            newSqlDelete.append(sqlDelete);
            if (sqlDelete.contains("WHERE")) {
                newSqlDelete.append(" AND ");
            } else {
                newSqlDelete.append(" WHERE ");
            }
            newSqlDelete.append(tableName);
            newSqlDelete.append(".");
            newSqlDelete.append(TENANT_COLUMN_NAME);
            newSqlDelete.append(" = ");
            newSqlDelete.append("?");

            ps = conn.prepareStatement(newSqlDelete.toString());
            int count = 1;
            for (Object param : params) {
                ps.setObject(count, param);
                count++;
            }
            ps.setString(count, tenantName);
            ps.execute();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(newSqlDelete.toString());
        }
    }

    private int getPositionTenantColumn(String tableName, Connection conn) throws SQLException {
        ResultSetMetaData metaData = conn.createStatement().executeQuery("SELECT * FROM " + tableName).getMetaData();
        int numeroColuna = metaData.getColumnCount();
        for (int i = 1; i <= numeroColuna; i++) {
            if (metaData.getColumnName(i).equals(TENANT_COLUMN_NAME)) {
                return i;
            }
        }
        return 0;
    }

    private boolean verifyExistenceTenant(String tenantName, Connection conn) throws SQLException {
        StringBuilder sql = new StringBuilder();
        DatabaseMetaData dbData = conn.getMetaData();
        ResultSet rs = dbData.getColumns(null, null, TENANT_TABLE_NAME, null);
        if (rs.next()) {
            sql.append("SELECT");
            sql.append(" ");
            sql.append(TENANT_COLUMN_NAME);
            sql.append(" FROM ");
            sql.append(TENANT_TABLE_NAME);
            sql.append(" WHERE ");
            sql.append(TENANT_COLUMN_NAME);
            sql.append(" = ");
            sql.append("?");

            ps = conn.prepareStatement(sql.toString());
            ps.setString(1, tenantName);
            rs = ps.executeQuery();
            return rs.next();
        } else {
            return false;
        }
    }

    private boolean verifyExistenceColumnTenant(String tableName, Connection conn) {
        boolean result;
        try {
            StringBuilder sql = new StringBuilder();
            sql.append("SELECT");
            sql.append(" COUNT");
            sql.append("(");
            sql.append(TENANT_COLUMN_NAME);
            sql.append(")");
            sql.append(" FROM ");
            sql.append(tableName);

            ResultSet rs = conn.createStatement().executeQuery(sql.toString());

            result = rs.next();
        } catch (SQLException ex) {
            return false;
        }
        return result;
    }

    private void insertIntoTenantTable(String tenantName, Connection conn) throws InsertTenantTableException {
        StringBuilder sql = new StringBuilder();
        try {
            sql.append("INSERT INTO ");
            sql.append(TENANT_TABLE_NAME);
            sql.append(" VALUES ");
            sql.append("(");
            sql.append("?");
            sql.append(")");

            ps = conn.prepareStatement(sql.toString());
            ps.setObject(1, tenantName);
            ps.execute();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new InsertTenantTableException();
        }
    }

    private void insertIntoTenantUserTable(String tableName, Connection conn) throws AlterTenantUserTableException {
        StringBuilder sql = new StringBuilder();

        try {
            sql.append("ALTER");
            sql.append(" TABLE ");
            sql.append(tableName);
            sql.append(" ADD");
            sql.append(" COLUMN ");
            sql.append(TENANT_COLUMN_NAME);
            sql.append(" VARCHAR");
            sql.append(" (");
            sql.append(TENANT_COLUMN_NAME_SIZE);
            sql.append(")");

            ps = conn.prepareStatement(sql.toString());
            ps.execute();
            ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
            throw new AlterTenantUserTableException();
        }
    }

    private void createTenantTable(Connection conn) throws CreateTenantTableException {
        StringBuilder sql = new StringBuilder();
        Statement stmt;
        try {
            DatabaseMetaData dbData = conn.getMetaData();
            ResultSet rs = dbData.getColumns(null, null, TENANT_TABLE_NAME, null);
            if (rs.next() == false) {
                sql.append("CREATE TABLE ");
                sql.append(TENANT_TABLE_NAME);
                sql.append(" (");
                sql.append(TENANT_COLUMN_NAME);
                sql.append(" ");
                sql.append(TENANT_COLUMN_NAME_TYPE);
                sql.append("(");
                sql.append(TENANT_COLUMN_NAME_SIZE);
                sql.append(")");
                sql.append(" PRIMARY KEY");
                sql.append(")");

                stmt = conn.createStatement();
                stmt.executeUpdate(sql.toString());
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
            throw new CreateTenantTableException();
        }
    }

    /**
     * Create a new Tenant.
     *
     * @param tenantId Tenant ID
     * @param conn JDBC Connection
     * @throws CreateTenantTableException If there's something wrong at the
     * connection or the permissions of the database user
     * @throws SQLException If there's something wrong at the connection or the
     * permissions of the database user
     * @throws InsertTenantTableException If there's something wrong at the
     * connection or the permissions of the database user
     */
    public void createTenant(String tenantId, Connection conn) throws CreateTenantTableException, SQLException, InsertTenantTableException {
        this.createTenantTable(conn);
        if (this.verifyExistenceTenant(tenantId, conn) == false) {
            this.insertIntoTenantTable(tenantId, conn);
        }
    }

    /**
     * Executes a single select query <b>without joins</b>. Accepts custom SQL
     * Queries.
     *
     * @param con JDBC Connection
     * @param sqlSelect A SQL select query. Ex: select * from users where id = ?
     * and psw = ?
     * @param params The values of the '?' params <b>in order</b>.
     * @param classTable The class that represents the table in the sql select
     * query.
     * @return A ResultSet of the prepared statement executeQuery method.
     * @throws MissingTenantUserAnnotationException If the TenantUser annotation
     * is not declared tenant in the objects in the parameters
     * @throws NullTenantException If the getTenant() method returns null
     */
    public ResultSet executeSelectQueryPreparedStatement(Connection con, String sqlSelect, List<Object> params, Tenantable classTable) throws InvalidOperationException, NullTenantException {
        String novaSql;
        sqlSelect = sqlSelect.toUpperCase();
        if (sqlSelect.contains("WHERE")) {
            novaSql = sqlSelect + " AND " + TENANT_COLUMN_NAME + " = ?";
        } else {
            novaSql = sqlSelect + " WHERE " + TENANT_COLUMN_NAME + " = ?";
        }

        try {
            ps = con.prepareStatement(novaSql.toString());

            int count = 1;
            if (params != null) {
                for (Object param : params) {
                    ps.setObject(1, param);
                    count++;
                }
            }

            String tenantName = null;
            try {
                tenantName = classTable.getTenant().getId();
            } catch (NullPointerException ex) {
                throw new NullTenantException();
            }
            ps.setString(count, tenantName);

            return ps.executeQuery();
        } catch (SQLException ex) {
            ex.printStackTrace();
            throw new InvalidOperationException(novaSql);
        }
    }
}
